﻿using System;
using System.Collections.Concurrent;

namespace MethodCaching
{
    /// <summary>
    /// A class that is capable of decorating a method with a cache.
    /// </summary>
    public static class CacheProvider
    {
        /// <summary>
        /// Decorates the target <paramref name="function"/> by returning a function
        /// delegate that points back to a cache for this function.
        /// </summary>        
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method to decorate.</param>
        /// <returns>A function delagate that represents the cached version of the target method.</returns>
        public static Func<TResult> Decorate<TResult>(Func<TResult> function)
        {
            var lazyValue = new Lazy<TResult>(function, true);
            return () => lazyValue.Value;            
        }
        
        
        /// <summary>
        /// Decorates the target <paramref name="function"/> by returning a function
        /// delegate that points back to a cache indexed by the method arguments.
        /// </summary>
        /// <typeparam name="T">The type of the parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method to decorate.</param>
        /// <returns>A function delagate that represents the cached version of the target method.</returns>
        public static Func<T,TResult> Decorate<T,TResult>(Func<T,TResult> function)
        {
            var cache = new ConcurrentDictionary<T, TResult>();
            return arg => cache.GetOrAdd(arg, function);
        }
        
        /// <summary>
        /// Decorates the target <paramref name="function"/> by returning a function
        /// delegate that points back to a cache indexed by the method arguments.
        /// </summary>
        /// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method to decorate.</param>
        /// <returns>A function delagate that represents the cached version of the target method.</returns>
        public static Func<T1,T2, TResult> Decorate<T1,T2, TResult>(Func<T1,T2, TResult> function)
        {
            var cache = new ConcurrentDictionary<Tuple<T1,T2>, TResult>();
            return (arg1,arg2) => cache.GetOrAdd(Tuple.Create(arg1,arg2),ck => function(arg1,arg2));
        }

        /// <summary>
        /// Decorates the target <paramref name="function"/> by returning a function
        /// delegate that points back to a cache indexed by the method arguments.
        /// </summary>
        /// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>        
        /// <typeparam name="T3">The type of the third parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method to decorate.</param>
        /// <returns>A function delagate that represents the cached version of the target method.</returns>
        public static Func<T1, T2,T3, TResult> Decorate<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> function)
        {
            var cache = new ConcurrentDictionary<Tuple<T1,T2,T3>, TResult>();
            return (arg1, arg2, arg3 ) => cache.GetOrAdd(Tuple.Create(arg1,arg2,arg3), ck => function(arg1, arg2, arg3));
        }

        /// <summary>
        /// Decorates the target <paramref name="function"/> by returning a function
        /// delegate that points back to a cache indexed by the method arguments.
        /// </summary>
        /// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>        
        /// <typeparam name="T3">The type of the third parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T4">The type of the forth parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method to decorate.</param>
        /// <returns>A function delagate that represents the cached version of the target method.</returns>
        public static Func<T1, T2, T3, T4, TResult> Decorate<T1, T2, T3, T4, TResult>(Func<T1, T2, T3, T4, TResult> function)
        {
            var cache = new ConcurrentDictionary<Tuple<T1,T2,T3,T4>, TResult>();
            return (arg1, arg2, arg3, arg4) => cache.GetOrAdd(Tuple.Create(arg1,arg2,arg3,arg4), ck => function(arg1, arg2, arg3, arg4 ));
        }
    }
}
